package com.heima.movement.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.apis.UsersApi;
import com.heima.models.common.Result;
import com.heima.models.movement.dtos.MovementAddDto;
import com.heima.models.movement.pojos.Movement;
import com.heima.models.movement.vos.MovementVo;
import com.heima.models.users.Users;
import com.heima.movement.mapper.MovementMapper;
import com.heima.movement.service.MovementService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 黑马程序员
 * @since 2023-07-04
 */
@Service
public class MovementServiceImpl extends ServiceImpl<MovementMapper, Movement> implements MovementService {
    @Autowired
    private MovementMapper movementMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private UsersApi usersApi;

    @Override
    public Result addMovement(MovementAddDto dto) {

        String content = dto.getContent();
        String images = dto.getImages();
        Double longitude = dto.getLongitude();
        Double latitude = dto.getLatitude();

        // 1. 判断入参是否为空
        if (StrUtil.isBlank(content) && StrUtil.isBlank(images)) {
            return Result.error("入参不能为空");
        }

        // 2. 将参数组装到实体类里面，写入到MySQL
        Movement movement = new Movement();
        movement.setComments(0);
        movement.setLikes(0);
        movement.setLove(0);
        movement.setCreatedTime(LocalDateTime.now());
        movement.setImages(images);
        movement.setContent(content);
        movement.setLocation(dto.getLongitude() + "," + dto.getLatitude());
        int insertResult = movementMapper.insert(movement);
        if (insertResult != 1) {
            return Result.error("插入失败");
        }

        // 3. 将写入到MySQL数据，缓存一份到Redis
        // 我要用什么数据结构去存储动态的缓存
        // 查询的时候应该用什么数据结构才能好查

        // 参数一:集合名
        // 参数二:写入集合的数据
        // 参数三:该条数据的分值（点赞+评论+收藏）
        // redisTemplate.opsForZSet().add("movement_list", JSON.toJSONString(movement), 0);

        // 借助RedisGeo，写入一条动态和位置的信息
        // 参数一：movement_location
        // 参数二：Point对象（发布动态时经纬度）
        // 参数三：坐标所对应的值（动态id）
        Point point = new Point(longitude, latitude);
        redisTemplate.opsForGeo().add("movement_location", point, movement.getId());

        // 在发布动态的时候，将添加好的这篇动态信息，缓存一份到redis的Hash结构里面

        // 参数一：hashKey -> movement_info_ist
        // 参数二：key -> movementId
        // 参数三：value -> 动态+用户 完整信息
        MovementVo vo = new MovementVo();
        BeanUtils.copyProperties(movement, vo);

        // 去用户服务那边，获取用户id对应的用户信息
        vo.setNickname("小黑黑");
        vo.setGender(2);
        vo.setEducation("本科");
        vo.setAvatar("1.png");
        vo.setAge(24);
        vo.setTag("年龄相仿");
        vo.setUserId(1002);

        redisTemplate.opsForHash().put("movement_info_ist", movement.getId(), JSON.toJSONString(vo));

        return Result.success("");
    }

    @Override
    public Result listMovementByLocation(Double longitude, Double latitude) {

        // 1. redis里面找周边五公里的动态
        // 参数一：存储坐标点的集合
        // 参数二：当前所在位置的圆的对象

        // 参数一：Point圆心对象
        Point point = new Point(longitude, latitude);

        // 参数二：DIstance距离对象
        Distance distance = new Distance(5, Metrics.KILOMETERS);

        Circle circle = new Circle(point, distance);

        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                        // 升序（由近到远）
                        .sortAscending()
                        // 最大查询数量
                        .limit(30)
                        .includeDistance()    // 显示距离
                        .includeCoordinates();  // 显示坐标

        // 参数三：查询条件（提取几条、排序规则、结果里面要不要显示直线距离）
        GeoResults<RedisGeoCommands.GeoLocation<String>> radius = redisTemplate.opsForGeo().radius("movement_location", circle, args);

        List<String> movementIdList = new ArrayList<>();
        Collection<Object> conditionList = new ArrayList<>();
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = radius.getContent();
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> item : content) {
            RedisGeoCommands.GeoLocation<String> content1 = item.getContent();
            // 这里的name字段，就是我们之前写到坐标里面的动态id
            // 我需要把结果集里面的所有的动态id，收集起来，用来给下一步查询使用
            String movementId = content1.getName();

            movementIdList.add(movementId);
            conditionList.add(movementId);
        }

        // 2. 根据上一步查出来的动态id，去获取对应的（用户信息 + 动态信息）

        // 3. 方案一：MySQl查询
        //List<MovementVo> voList = getMovementVoList(movementIdList);

        // 4. 方案二：Redis查询
        // 参数一：hash分组名称
        // 参数二：作为查询条件的集合
        List<MovementVo> voList = new ArrayList<>();
        List<Object> movementInfoList = redisTemplate.opsForHash().multiGet("movement_info_ist", conditionList);
        for (Object o : movementInfoList) {
            String data = (String) o;
            MovementVo movementVo = JSON.parseObject(data, MovementVo.class);
            voList.add(movementVo);
        }

        return Result.success(voList);
    }

    public List<MovementVo> getMovementVoList(List<String> movementIdList) {
        // 3.1 当前服务内动态表取查询动态信息（完整动态信息）
        LambdaQueryWrapper<Movement> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(Movement::getId, movementIdList);
        List<Movement> movements = movementMapper.selectList(wrapper);
        if (CollectionUtil.isEmpty(movements)) {
            return null;
        }

        // 3.2 把集合里面的用户id，全部提取出来，然后到用户服务去查询获取
        List<Integer> userIdList = movements.stream().filter(Objects::nonNull).map(Movement::getUserId).collect(Collectors.toList());

        System.out.println(userIdList);

        // 3.3 用户信息在当前数据库查不到，那么要怎么获取呢？（用Feign去调用用户服务的接口）
        // 3.3.1 @EnableFeignClients(basepackages="") -> 加在发起方
        // 3.3.2 用户controller定义一个接口
        // 3.3.3 给controller接口配置一个 inteface 类，类上面添加 FeignClient()所属服务, 类里面加方法，方法上要设置该方法的URL请求地址
        String ids = StrUtil.join(",", userIdList);
        Result result = usersApi.getUserListByIdList(ids);

        Object data = result.getData(); // -> List<Users>
        // Object -> JSON -> JSON字符串
        // JSON字符串 -> JSON -> List
        String dataString = JSON.toJSONString(data);
        List<Users> users = JSON.parseArray(dataString, Users.class);

        // 动态集合
        // { "动态id": 123, "动态内容": "", "用户id": 1002}
        // { "动态id": 124, "动态内容": "", "用户id": 1005}

        // 用户集合
        // { "用户id": 1002, "用户名": "小黑黑" }
        // { "用户id": 1005, "用户名": "小白白" }

        // 最终得到的集合
        // { "动态id": 123, "动态内容": "", "用户id": 1002, "用户名": "小黑黑" }
        // { "动态id": 124, "动态内容": "", "用户id": 1005, "用户名": "小白白" }

        // 1. 遍历动态集合
        // 2. 每次遍历之后，拿出遍历出来这条数据里面的用户id
        // 3. 拿着这个用户id，到用户集合里面去找，对应的用户信息
        // 4. 拿着id，到用户集合里面去遍历

        List<MovementVo> voList = new ArrayList<>();
        for (Movement movement : movements) {
            if (movement == null) {
                continue;
            }

            MovementVo vo = new MovementVo();
            BeanUtils.copyProperties(movement, vo);

            Integer userId = movement.getUserId();

            for (Users user : users) {
                if (user == null) {
                    continue;
                }

                String id = user.getId();
                if (userId.toString().equals(id)) {
                    // 如果用户列表里面这个id，和动态列表里面那个id 比对上了，证明当前拿到的这个用户信息是我想要的
                    // 把当前遍历到这个用户信息提取出来，和外面的那个动态信息做一个拼接
                    vo.setAge(user.getAge());
                    vo.setAvatar(user.getAvatar());
                    vo.setEducation(user.getEducation());
                    vo.setGender(user.getGender());
                    vo.setNickname(user.getNickname());
                }
            }

            voList.add(vo);
        }

        return voList;
    }
}
